/*
 * FNISVGEditor - JavaScript - based editor for SVG graphics 
 * Copyright (C) 2008 - 2009 Antti Leppä / Foyt
 * http://www.foyt.fi
 * 
 * License: 
 * 
 * Licensed under GNU Lesser General Public License Version 2.1 or later (the "LGPL") 
 * http://www.gnu.org/licenses/lgpl.html
 */

var SVGLocale = new fni.locale.FNILocale();

/**
 * @class
 * @extends GUIComponent
 * @constructor
 */
FNISVGEditor = Class.create(GUIComponent, /**
 * @scope FNISVGEditor.prototype
 */
{
  /**
   * Constructor
   *
   * @param {Object} $super superclass
   * @param {Hash} options
   */
  initialize: function($super, options){
    $super(options);
    
    this.domNode = new Element("div", {
      className: "fniSVGEditor"
    });
    
    this._basePath = this._resolveRelativePath(window.location.href, this.getComponentOptions().basePath);
    this._configurationManager = new FNIConfigurationManager(options.configuration);
    var languages = this._configurationManager.getValue("languages");
    
    for (var i = 0; i < languages.length; i++)
      SVGLocale.loadLocale(languages[i], this._basePath + "/lang/" + languages[i] + ".json");

    var language = this.getComponentOptions().language ? this.getComponentOptions().language : this._configurationManager.getValue("defaultLanguage");
    SVGLocale.setLanguage(language);
        
    this._availableToolbarItems = new Hash();
    this._toolbar = new FNIToolbar({});
    
    this._viewPort = new FNISVGEditorViewPort(this);
    this._svgDocument = new fi.foyt.svg.svgdom.FNISVGDocument();
    this._viewComponent = new FNISVGEditorView(this);
    
    this._statusBar = new FNISVGEditorStausBar(this);
    
    this.addGUIComponent(this._toolbar);
    this.addGUIComponent(this._viewComponent);
    this.addGUIComponent(this._statusBar);
    
    this._pluginManager = new FNISVGEditorPluginManager(this);
    this._styleManager = new FNISVGEditorStyleManager();
    this._selectionManager = new FNISelectionManager(this);
    this._undoManager = new FNISVGEditorUndoManager(this);
     
    this.disableSelection();
    this.disableContextMenu();
    
    this._mouseDragging = false;
    this._mouseDown = false;
    this._mouseOverElement = null;

    var statusBarGroups = this.getConfigurationManager().getValue('statusbargroups');
    for (var i in statusBarGroups) {
      var groupConf = statusBarGroups[i];
      this.getStatusBar().registerGroup(i, groupConf.type);
    }
    
    this.addListener("DOMStatusChange", this, this._onDOMStatusChange);    
    this.getPluginManager().addListener("pluginsLoaded", this, this._onPluginsLoaded);
    
    this._ctrlDown = false;
    this._shiftDown = false;
    this._altDown = false;
    this._lastClick = -1;
  },
  /**
   * Deinitializes component
   *
   * @param {Class} $super superclass
   */
  deinitialize: function($super){
    $super();
  },
  openDialog: function (options) {
    var dialog = new FNISVGEditorDialog(this, options);
    var documentBody = document.getElementsByTagName('body')[0];    
    documentBody.appendChild(dialog.domNode);
    dialog.componentOnDOM();
    return dialog;
  },
  openWindow: function (options) {
    var window = new FNISVGEditorWindow(this, options);
    var documentBody = document.getElementsByTagName('body')[0];
    documentBody.appendChild(window.domNode);
    window.componentOnDOM();
    return window;
  },  
  /**
   * Sets editor height  
   */  
  setHeight: function (height) {
    var toolbarHeight = this._toolbar.domNode.getHeight();
    var statusBarHeight = this._statusBar.domNode.getHeight();
    var viewHeight = height - (toolbarHeight + statusBarHeight);    
    this._viewComponent.domNode.setStyle({
      'height': viewHeight + 'px'
    });
    
    var viewWidth = this._viewComponent.domNode.getWidth();
    var viewBox = this.getViewPort().getViewBox();
    this.getViewPort().setViewBox(viewBox.x, viewBox.y, viewWidth, viewHeight);
  },  
  getEditorView: function() {
	  return this._viewComponent;  
  },
  /**
   * Returns stylemanager
   *
   * @return {FNISVGEditorStyleManager}
   */
  getStyleManager: function(){
    return this._styleManager;
  },
  /**
   * Returns selection manager
   * @return {FNISelectionManager}
   */
  getSelectionManager: function(){
    return this._selectionManager;
  },
  /**
   * Returns configuration manager
   * @return {FNIConfigurationManager}
   */
  getConfigurationManager: function(){
    return this._configurationManager;
  },
  /**
   * Returns undo manager
   * @return {FNISVGEditorUndoManager}
   */
  getUndoManager: function(){
    return this._undoManager;
  },
  /**
   * Returns SVGDocument
   *
   * @return {fi.foyt.svg.svgdom.FNISVGDocument}
   */
  getSVGDocument: function(){
    return this._svgDocument;
  },
  /**
   * Returns Viewport
   *
   * @return {FNISVGEditorViewPort}
   */
  getViewPort: function(){
    return this._viewPort;
  },
  /**
   * Returns a plugin manager
   * @return {FNISVGEditorPluginManager}
   */
  getPluginManager: function(){
    return this._pluginManager;
  },
  /**
   * Returns status bar component
   * @return {FNISVGEditorStatusBar}
   */
  getStatusBar: function(){
    return this._statusBar;
  },
  /**
   * returns editor base path
   * @return {String}
   */
  getBasePath: function(){
    return this._basePath; 
  },
  /**
   * @return {FNIToolbar}
   */
  getToolbar: function(){
    return this._toolbar;
  },
  /**
   * registers new toolbar item
   */  
  registerToolbarItem: function(name, item) {
    this._availableToolbarItems.set(name, item);
  },
  changeCursor: function (cursor) {
    // this._previousCursor = this._viewComponent.domNode.style.cursor;
    this._viewComponent.domNode.style.cursor = cursor;  
  },
  resetCursor: function (cursor) {   
    this._viewComponent.domNode.style.cursor = 'default';//;this._previousCursor;  
  },
  getDataAsXML: function () {
    var pageSize = this.getViewPort().getPageSize();  
    return this.getSVGDocument().getAsXML(pageSize.width, pageSize.height);
  },
  /**
   * Creates new SVG Element
   * @param {Object} name
   */
  createSVGElement: function(name){
    var element = this.getSVGDocument().createElement(name);
    if (name.toUpperCase() != 'G') 
      element.setAttribute('style', this._styleManager.asString());
    return element;
  },
  /**
   * Appends new SVG Element to svg document
   *
   * @param {SVGDOMNode} element element to be appended
   */
  appendSVGElement: function(element) {
    this.getSVGDocument().appendElement(this.getSVGDocument().getCurrentLayer(), element);
    this.startSVGElementObserving(element);
  },
  /**
   * Removes SVG Element
   *
   * @param {SVGDOMElement} element element to be removed
   */
  removeElement: function(element){
    this.getSVGDocument().removeElement(element);
    this.stopSVGElementObserving(element);
  },
  startSVGElementObserving: function(element){
    /** TODO: Implement this **/
  },
  stopSVGElementObserving: function(element){
    /** TODO: Implement this **/
  },
  loadFromXML: function (xmlData) {
    var svgDocument = null;
    
    if (Prototype.Browser.IE) {
      svgDocument = new ActiveXObject("Microsoft.XMLDOM");
      svgDocument.async = false;
      svgDocument.loadXML(xmlData);                                 
    } else {
      parser = new DOMParser();
      svgDocument = parser.parseFromString(xmlData, "text/xml");
    }
    
    var layers = this.getSVGDocument().getLayers();
    for (var i = 0; i < layers.length; i++)
      this.getSVGDocument().removeLayer(layers[i]);
    
    var currentLayer = this.getSVGDocument().createLayer("Layer #1");
    this.getSVGDocument().setCurrentLayer(currentLayer);
    
    this.appendExternalSVGDocument(svgDocument);
    
    if (this.getSVGDocument().getLayers().length > 1) {
      var hasElements = false; 
      var child = currentLayer.firstChild;
      while ((hasElements == false) && (child != null)) {
        if (child.nodeType == 1)
          hasElements = true;
        else 
          child = child.nextSibling;
      }
      
      if (hasElements == false)
        this.getSVGDocument().removeLayer(currentLayer);              
    }
    
    var firstLayer = this.getSVGDocument().getLayers()[0];
    this.getSVGDocument().setCurrentLayer(firstLayer);
                  
    if (svgDocument.documentElement != null) {
      var pageWidth = parseFloat(svgDocument.documentElement.getAttribute("width"));
      var pageHeight = parseFloat(svgDocument.documentElement.getAttribute("height"));
      
      if ((pageWidth > 0) && (pageHeight > 0)) {
        this.getViewPort().setPageSize(pageWidth, pageHeight);
        this.getViewPort().zoomToPage();     
      }
    }
  },
  appendExternalSVGDocument: function(svgDocument){
    for (var i = 0; i < svgDocument.documentElement.childNodes.length; i++) {
      this.appendExternalSVGElement(this.getSVGDocument().getCurrentLayer(), svgDocument.documentElement.childNodes[i], true, false);
    }
  },
  appendExternalSVGElement: function(parentNode, svgNode, deep, grouped){
    if (fi.foyt.svg.svgdom.FNISVGDOMUtils.isValidSVGElement(svgNode)) {
      var nodeType = 'normal';
      if (svgNode.tagName.toUpperCase() == 'G') {
        if (svgNode.getAttribute('fnigrouptype') == 'layer')
          nodeType = 'layer';
        else if (svgNode.getAttribute('inkscape:groupmode') == 'layer')
          nodeType = 'inkscapeLayer';
      }
  
      switch (nodeType) {
      case 'normal':
        var node = this.importSVGElement(parentNode, svgNode, grouped);
  
        if (deep == true) {
          for ( var i = 0; i < svgNode.childNodes.length; i++)
            this.appendExternalSVGElement(node, svgNode.childNodes[i], deep, grouped || (node.tagName.toUpperCase() == 'G'));
        }
        break;
      case 'layer':
        var layer = this.getSVGDocument().createLayer(svgNode.getAttribute('fnilayertitle'));
        if (deep == true) {
          for ( var i = 0; i < svgNode.childNodes.length; i++)
            this.appendExternalSVGElement(layer, svgNode.childNodes[i], deep, grouped);
        }
        break;
      case 'inkscapeLayer':
        var layer = this.getSVGDocument().createLayer(svgNode.getAttribute('inkscape:label'));
        if (deep == true) {
          for ( var i = 0; i < svgNode.childNodes.length; i++)
            this.appendExternalSVGElement(layer, svgNode.childNodes[i], deep, grouped);
        }
        break;
      }
    }
  },
  importSVGElement: function(parentNode, element, grouped){
    var node = this.getSVGDocument().importElement(element, false);
    this.getSVGDocument().appendElement(parentNode, node);
    
    fi.foyt.svg.svgdom.FNISVGDOMUtils.removeIllegalAttributes(this.getSVGDocument(), node);
    
    if (grouped == false) 
      this.startSVGElementObserving(element);
    
    return node;
  },
  /**
   * Returns mouse position in editor
   *
   * @return {Array} hash where values are stored in x and y variables
   */
  getMouseInfo: function(){
    return {
      x: this._mouseEditorX,
      y: this._mouseEditorY,
      lastX: this._mouseLastX,
      lastY: this._mouseLastY,
      dragStartX: this._mouseDragStartX,
      dragStartY: this._mouseDragStartY
    };
  },
  /**
   * Returns view transformation info in 1x2 matrix [0] = x transformation ratio and [1] = y transformation ratio
   */
  getViewTransform: function(){
    var viewDimensions = this._viewComponent.domNode.getDimensions();
    var portDimensions = this.getViewPort().getViewBox();
    return [(portDimensions.width / viewDimensions.width), (portDimensions.height / viewDimensions.height)];
  },
  _resolveRelativePath: function (baseDir, path) {
    if ((path[0] == '/')||(path.indexOf("http://") == 0)||(path.indexOf("https://") == 0)) 
      return path;
    else {    
      if (baseDir[baseDir.length - 1] != '/')   
        baseDir = baseDir.substring(0, baseDir.lastIndexOf('/') + 1);

      if (path.indexOf("..") == 0) {
        var dpTokens = baseDir.split("/");
        var pTokens = path.split("/");

        if (dpTokens[dpTokens.length - 1] == '')
          dpTokens.splice(dpTokens.length - 1, 1);

        if (pTokens[pTokens.length - 1] == '')
          pTokens.splice(pTokens.length - 1, 1);

        while (pTokens[0] == '..') {
          pTokens.splice(0, 1);
          dpTokens.splice(dpTokens.length - 1, 1);
        }

        var result = "";
        for (var i = 0; i < dpTokens.length; i++)
          result += dpTokens[i] + '/';
        for (var i = 0; i < pTokens.length; i++) {
          result += pTokens[i]; 
          if ((path[path.length -1] == '/')||(i < (pTokens.length - 1)))
            result += '/';
        }

        return result;
      } else
        return baseDir + path;
    }
  },
  _onDOMStatusChange: function (event) {    
    if (event.action == 'added') {    
      var pageConf = this.getConfigurationManager().getValue("page");
      this.getViewPort().setPageSize(pageConf.width, pageConf.height);
      
      var viewDimensions = this._viewComponent.domNode.getDimensions();
      this.getSVGDocument().setViewBox(0, 0, viewDimensions.width, viewDimensions.height);
      this.getViewPort().centerOnPage();

      var _this = this;
      
  	  this.getSVGDocument().getRootElement().addEventListener("mousemove", function(evt){
  	    _this._onSVGDocumentMouseMove(evt);
  	  }, false);
  	  
  	  if (Prototype.Browser.IE) {
        // TODO: When mouse goes out of editorview this one fails...   	    
  	    this.getSVGDocument().getRootElement().addEventListener("mouseup", function(evt){
          _this._onWindowMouseUp(evt);
        }, false);
  	  } else {  	  
  	    Event.observe(window, "mouseup", function(evt){
          _this._onWindowMouseUp(evt);
        });
  	  }
  	      
  	  this.getSVGDocument().getRootElement().addEventListener("mousedown", function(evt){
  	    _this._onSVGDocumentMouseDown(evt);
      }, false);
  	  
  	  this._keyDownListener = this._onKeyDown.bindAsEventListener(this);
  	  Event.observe(window, "keydown", this._keyDownListener);	  
  	  this._keyUpListener = this._onKeyUp.bindAsEventListener(this);
  	  Event.observe(window, "keyup", this._keyUpListener);
  	  
  	  this.getPluginManager().loadPlugins(this.getConfigurationManager().getValue("plugins/active"));
    }    
  },
  _onPluginsLoaded: function (event) {
    this._pluginsLoaded = true;
    this._initializeToolbar();
    this.getViewPort().zoomToPage();
    this.fire("editorInitialized", { });
  },
  _initializeToolbar: function () {
    var toolbarConf = this.getConfigurationManager().getValue('toolbar');
    for (var i = 0; i < toolbarConf.length; i++) {
      var toolbarGroupConf = toolbarConf[i];
      var mode = toolbarGroupConf[0] == 'g' ? FNIButtonGroup.MODE_SINGLEDOWN : FNIButtonGroup.MODE_NORMAL;
      
      var toolbarGroup = new FNIButtonGroup({
        mode: mode
      });
      
      for (var j = 1; j < toolbarGroupConf.length; j++) {
        var buttonName = toolbarGroupConf[j];
        var button = this._availableToolbarItems.get(buttonName);
        if (button)
          toolbarGroup.addButton(button);
        else 
          throw new Error("Button \"" +  buttonName + "\" does not exist");
      }
      
      this.getToolbar().addButtonGroup(toolbarGroup);
      if (i < (toolbarConf.length - 1))
        this.getToolbar().addSeparator();      
    }
  },
  /**
   * @private
   */
  _SVGElementClicked: function(event){
    this.fire("svgelementclicked", {
      element: event.currentTarget
    });
  },
  /**
   * @private
   */
  _mouseOverSVGElement: function(event){
    this.fire("mouseoversvgelement", {
      element: event.currentTarget
    });
  },
  /**
   * @private
   */
  _mouseOutSVGElement: function(event){
    this.fire("mouseoutsvgelement", {
      element: event.currentTarget
    });
  },
  /**
   * @private
   */
  _onSVGDocumentMouseMove: function(event){
    this._mouseClicking = false;
    this._mouseDblClicking = false;
    this._mouseLastX = this._mouseEditorX;
    this._mouseLastY = this._mouseEditorY;
    
    var viewTransform = this.getViewTransform();
    var portDimensions = this.getViewPort().getViewBox();

    var viewOffsets = this._viewComponent.domNode.cumulativeOffset();

    var screenMouseX = Event.pointerX(event);
    var screenMouseY = Event.pointerY(event);
  
    this._mouseEditorX = ((screenMouseX - viewOffsets.left) * viewTransform[0]) + portDimensions.x;
    this._mouseEditorY = ((screenMouseY - viewOffsets.top) * viewTransform[1]) + portDimensions.y;
    
    var button = event.which ? button = event.which : event.button;
    
    if (this._mouseDragging == true) {
      this._fireMouseEvent("mousedragging", event, {
        dragStartX: this._mouseDragStartX,
        dragStartY: this._mouseDragStartY,
        button: button
      });
    }
    else {
      if ((this._mouseDown == true) && (this._mouseDragging == false)) {
        this._mouseDragging = true;
        this._mouseDragStartX = this._mouseEditorX;
        this._mouseDragStartY = this._mouseEditorY;
        this._fireMouseEvent("mousedragstart", event, {
          button: button
        });
      }
    }
    
    var overElement = this.getViewPort().getElementAt(this._mouseEditorX, this._mouseEditorY);
    
    if (this._mouseOverElement != overElement) {    
      if (this._mouseOverElement != null) {
        this.fire("mouseOutElement", {
          element: this._mouseOverElement
        });
      }
      
      if (overElement != null) {
        this.fire("mouseOverElement", {
          element: overElement
        });        
      }
      
      this._mouseOverElement = overElement;  
    } 
    
    this._fireMouseEvent("mousemove", event);    
  },
  _fireMouseEvent: function(event, originalEvent, extraInfo){
    var info = {
      ctrlKey: originalEvent.ctrlKey,
      shiftKey: originalEvent.shiftKey,
      altKey: originalEvent.altKey,
      mouseEditorX: this._mouseEditorX,
      mouseEditorY: this._mouseEditorY,
      mouseLastX: this._mouseLastX,
      mouseLastY: this._mouseLastY
    };
    
    if (extraInfo) 
      Object.extend(info, extraInfo);
    
    this.fire(event, info);
  },
  /**
   * @private
   */
  _onWindowMouseUp: function(event){
    var button = event.which ? button = event.which : event.button;
    if (this._mouseClicking == true) {
      this._mouseClicking = false;
      
      var now = new Date().getTime();
      var dblClick = false;
      
      if (((now - this._lastClick) < 300) && (this._mouseDblClicking == true)) {
        dblClick = true;
        this._mouseDblClicking = false;
      } else {
        this._mouseDblClicking = true;
      }
      
      if (dblClick) {
        this._fireMouseEvent("doubleclick", event, {
          button: button
        });
      } else {      
        this._fireMouseEvent("mouseclick", event, {
          button: button
        });
      }
      
      this._lastClick = now;
       
      if (this._mouseOverElement != null) {
        this._fireMouseEvent("elementClick", event, {
          element: this._mouseOverElement,
          button: button
        });
      }                 
    } else if (this._mouseDragging == true) {
      this._mouseDragging = false;
      
      this._fireMouseEvent("mousedragend", event, {
        dragStartX: this._mouseDragStartX,
        dragStartY: this._mouseDragStartY,
        button: button
      });
    }  
      
    this._fireMouseEvent("mouseup", event, {
      button: button
    });
        
    this._mouseDragStartX = 0;
    this._mouseDragStartY = 0;
    this._mouseDown = false;    
  },
  /**
   * @private
   */
  _onSVGDocumentMouseDown: function(event){
    var button = event.which ? button = event.which : event.button;
    this._mouseDown = true;
    this._fireMouseEvent("mousedown", event, {
      button: button
    });
    
    this._mouseClicking = true;
  },
  _onKeyDown: function (event) {
	switch (event.keyCode) {
	  case 16:
		this._shiftDown = true; 
	  break;
	  case 17:
		this._ctrlDown = true; 
	  break;
	  case 18:
		this._altDown = true; 
	  break;
	  default:
	    this.fire("keydown", {
	      keyCode: event.keyCode,
	      shift: this._shiftDown,
	      ctrl: this._ctrlDown,
	      alt: this._altDown
	    });  
	  break;
	}	
  },
  _onKeyUp: function (event) {
	switch (event.keyCode) {
	  case 16:
		this._shiftDown = false; 
	  break;
	  case 17:
		this._ctrlDown = false; 
	  break;
	  case 18:
		this._altDown = false; 
	  break;
	  default:
	    this.fire("keyup", {
	      keyCode: event.keyCode,
	      shift: this._shiftDown,
	      ctrl: this._ctrlDown,
	      alt: this._altDown
	    });  
	  break;
	}	
  }
});
